package com.example.demo.security.otp;

import com.example.demo.security.otp.api.*;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * TOTP类实现了基于时间的一次性密码算法。
 * 该类主要用于生成和验证基于时间的一次性密码(TOTP)。
 *
 * @author houp
 */
public class Totp {

    private final String secret;
    private final Clock clock;
    private static final int DELAY_WINDOW = 1;

    /**
     * TOTP类的构造函数，使用默认的时钟。
     *
     * @param secret 用于生成TOTP的密钥。
     */
    public Totp(String secret) {
        this.secret = secret;
        clock = new Clock();
    }

    /**
     * TOTP类的构造函数，允许自定义时钟。
     *
     * @param secret 用于生成TOTP的密钥。
     * @param clock  用于获取当前时间间隔的自定义时钟。
     */
    public Totp(String secret, Clock clock) {
        this.secret = secret;
        this.clock = clock;
    }

    /**
     * 生成用于客户端配置TOTP的URI。
     *
     * @param label TOTP的标签，通常是一个用户名。
     * @return 包含TOTP配置信息的URI。
     */
    public String uri(String label) {
        return uri(label, "");
    }

    /**
     * 生成用于客户端配置TOTP的URI，包括发行者信息。
     *
     * @param label  TOTP的标签，通常是一个用户名。
     * @param issuer TOTP的发行者，例如一个公司或服务提供商的名称。
     * @return 包含TOTP配置信息的URI。
     */
    public String uri(String label, String issuer) {
        return uri(label, issuer, "");
    }

    /**
     * 生成用于客户端配置TOTP的URI，包括发行者和图像URL信息。
     *
     * @param label  TOTP的标签，通常是一个用户名。
     * @param issuer TOTP的发行者，例如一个公司或服务提供商的名称。
     * @param imgUrl 与TOTP相关的图像URL。
     * @return 包含TOTP配置信息的URI。
     */
    public String uri(String label, String issuer, String imgUrl) {
        // 构建TOTP的URI字符串
        try {
            StringBuilder sb = new StringBuilder("otpauth://totp/");
            if (label == null || label.isEmpty()) {
                throw new IllegalArgumentException("label is empty");
            }
            if (secret == null || secret.isEmpty()) {
                throw new IllegalArgumentException("secret is empty");
            }
            sb.append(URLEncoder.encode(label, "UTF-8")).append("?secret=").append(this.secret);
            if (issuer != null && !issuer.isEmpty()) {
                sb.append("&issuer=").append(URLEncoder.encode(issuer, "UTF-8"));
            }
            if (imgUrl != null && !imgUrl.isEmpty()) {
                sb.append("&image=").append(URLEncoder.encode(imgUrl, "UTF-8"));
            }
            return sb.toString();
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalArgumentException(ex.getMessage(), ex);
        }
    }

    /**
     * 根据当前时间生成TOTP。
     *
     * @return 当前时间间隔的TOTP。
     */
    public String now() {
        // 生成基于当前时间的TOTP代码，并进行左填充
        return leftPadding(hash(secret, clock.getCurrentInterval()));
    }

    /**
     * 验证给定的TOTP是否有效。
     *
     * @param otp 待验证的TOTP代码。
     * @return 如果TOTP有效，则返回true；否则返回false。
     */
    public boolean verify(String otp) {
        // 将OTP转换为长整型以进行验证
        long code = Long.parseLong(otp);
        long currentInterval = clock.getCurrentInterval();
        int pastResponse = Math.max(DELAY_WINDOW, 0);
        for (int i = pastResponse; i >= 0; --i) {
            int candidate = generate(this.secret, currentInterval - i);
            if (candidate == code) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据密钥和时间间隔生成TOTP代码。
     *
     * @param secret   用于生成TOTP的密钥。
     * @param interval 当前的时间间隔。
     * @return 生成的TOTP代码。
     */
    private int generate(String secret, long interval) {
        // 调用hash函数生成TOTP代码
        return hash(secret, interval);
    }

    /**
     * 使用HMAC-SHA1算法和密钥对时间间隔进行散列，返回散列结果的中间4字节转换为整型的结果。
     *
     * @param secret   用于生成TOTP的密钥。
     * @param interval 当前的时间间隔。
     * @return 散列结果转换后的整型值。
     */
    private int hash(String secret, long interval) {
        if (secret == null || secret.isEmpty()) {
            throw new IllegalArgumentException("secret is empty");
        }
        byte[] hash = new byte[0];
        try {
            hash = new Hmac(Hash.SHA1, Base32.decode(secret), interval).digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (Base32.DecodingException e) {
            e.printStackTrace();
        }
        // 从散列结果中提取中间4字节，转换为整型值
        return bytesToInt(hash);
    }

    /**
     * 从字节数组中提取4字节，转换为整型值。
     *
     * @param hash 散列结果的字节数组。
     * @return 提取的4字节转换后的整型值。
     */
    private int bytesToInt(byte[] hash) {
        // 根据RFC 6238，使用最后4字节，并将它们转换为整型值
        int offset = hash[hash.length - 1] & 0xf;
        int binary = ((hash[offset] & 0x7f) << 24) |
                ((hash[offset + 1] & 0xff) << 16) |
                ((hash[offset + 2] & 0xff) << 8) |
                (hash[offset + 3] & 0xff);
        return binary % Digits.SIX.getValue();
    }

    /**
     * 对生成的TOTP代码进行左填充，确保其长度为6位。
     *
     * @param otpCode 生成的TOTP代码。
     * @return 左填充后的TOTP代码。
     */
    private String leftPadding(int otpCode) {
        // 使用String.format进行左填充
        return String.format("%06d", otpCode);
    }

}
